package run;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import run.core.NIL;


/**
 * 
 * Facets are contextualized to an owner cell and have a handle
 * on both the enclosing (client/user) cell and their owner.
 * Facets manage the flow of data, acting as gatekeepers on borders,
 * only permitting penetration of cellular membranes when
 * the data conditions are met.
 * 
 * This is called "Bind and Wait". 
 * On the INBOUND direction:
 * 		If a cell facet is used by an enclosing cell that creates 
 * 		five different binds on it, the facet will wait until it has 
 *  	received values from ALL FIVE SOURCES before allowing 
 *  	absorption of those values into its owner.
 *  
 *  On the OUTBOUND direction:
 *  	Similar restrictions apply against values coming from the
 *  	interior of the cell, with an additional condition:
 *  		Values will only be released when the current computation
 *  		has completed every branch.
 *  
 *   
 * 
 * ADD FACET'S HANDLE ON ITS OWNING/CONTEXTUALIZING CELL
 * 
 * @author Thomas Chau
 */
public class Facet {
	private String name;
	private Cell owner;
	boolean isInputFacet;
	
	// places this facet might be bound to (as in, places it owes)
	List<Facet> destinations;
	
	// places this facet is bound to (places that owe it)
	List<Facet> sources;
	
	Map<Facet, Queue<Object>> inPort;
	
	public Facet(String name, Cell owner, boolean isInputFacet) {
		this.owner = owner;
		this.name = name;

		this.isInputFacet = isInputFacet;
		this.inPort = new HashMap<Facet, Queue<Object>>();
		
		destinations = new ArrayList<Facet>();
		sources = new ArrayList<Facet>();
		
		owner.addFacet(this, isInputFacet);
	}
	
	public Cell getOwner() {
		return owner;
	}
	public String getName() {
		return name;
	}
	
	public String toString() {
		return name + ":" + hashCode() + "(" +  owner.cellTypeName + ": " +  owner.hashCode() + ")";
	}

	/**
	 * A list of outbound data ships out. (Guaranteed ready)
	 * @return
	 */
	public List<Object> dispatchOutbound() {
		List<Object> outbound = new ArrayList<Object>();
		for (Facet f : sources) {
			Queue ourQ = inPort.get(f);
			
			if (ourQ.size() == 0)
				System.out.println("crap");
			
			Object head = ourQ.remove();
			outbound.add(head);
		}
		return outbound;
	}
	
	public List<Object> dispatchOutboundEntire() {
		List<Object> outbound = new ArrayList<Object>();
		for (Facet f : sources) {
			Queue ourQ = inPort.get(f);
			outbound.addAll(ourQ);
			ourQ.clear();
		}
		return outbound;
	}

	/**
	 * Accepts the values from another facet to the port.
	 * 
	 * @param stimmer
	 * @param outboundValues
	 * @return whether the facet is ready after receiving the values
	 */
	public boolean receive(Facet stimmer, List<Object> inboundValues) {
		inPort.get(stimmer).addAll(inboundValues);
		boolean ready = true;
		
		// for all source facets
		// if they ALL have a queue size > 0 of values waiting in the dock... we're good
		for (Facet f : sources) {
			Queue<Object> inBoundFromSrc = inPort.get(f);
			if (inBoundFromSrc.size() == 0) {
				ready = false;
			}
		}
		
		return ready;
	}

	public void addDestination(Facet right) {
		destinations.add(right);
	}

	public void addSource(Facet left) {
		sources.add(left);
		inPort.put(left, new LinkedList<Object>());
	}
	
	/**
	 * Inserts NIL into all ports. (Dead Branch)
	 */
	public void nillify() {
		for (Facet f : sources) {
//			System.out.println(name + " nil " + f.name);
			inPort.get(f).add(new NIL());
		}
	}
	
	public void clearNils() {
		for (Facet f : sources) {
			Queue<Object> queue = inPort.get(f);
			NIL nil = new NIL();
			while (queue.contains(nil)) {
				queue.remove(nil);
			}
		}
	}

	public boolean isReady() {
		boolean ready = true;
		
		for (Facet f : sources) {
			Queue<Object> inBoundFromSrc = inPort.get(f);
			if (inBoundFromSrc.size() == 0) {
				ready = false;
			}
		}
		return ready;
	}

}
